﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;

namespace SerialTest
{
    public enum DataEntryType
    {
        HexValues,
        BitPattern,
        Random
    };


    public struct JobDetails
    {
        public string m_COMPort;
        public int m_BaudRate;
        public Parity m_Parity;
        public int m_DataBits;
        public StopBits m_StopBits;
        public Encoding m_Encoding;
        public int m_Timeout;
        public Handshake m_Handshake;
        public string m_HexValues;
        public byte m_BitValue;
        public int m_Delay;
        public int m_Iterations;
        public DataEntryType m_DataEntryType;
    }

    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();

            //
            // Initialise default values
            PortName.Text = "COM7";
            PortBaudRate.Text = "9600";
            PortWriteTimeout.Text = "-1";

            PortParity.Items.Add(Parity.Even);
            PortParity.Items.Add(Parity.Odd);
            PortParity.Items.Add(Parity.None);
            PortParity.Items.Add(Parity.Mark);
            PortParity.Items.Add(Parity.Space);
            PortParity.SelectedIndex = 0;

            PortStopBits.Items.Add(StopBits.One);
            PortStopBits.Items.Add(StopBits.Two);
            PortStopBits.Items.Add(StopBits.OnePointFive);
            PortStopBits.SelectedIndex = 0;

            PortEncoding.Items.Add(Encoding.ASCII);
            PortEncoding.Items.Add(Encoding.UTF8);
            PortEncoding.Items.Add(Encoding.Unicode);
            PortEncoding.Items.Add(Encoding.UTF32);
            PortEncoding.SelectedIndex = 0;

            PortHandShake.Items.Add(Handshake.None);
            PortHandShake.Items.Add(Handshake.RequestToSend);
            PortHandShake.Items.Add(Handshake.RequestToSendXOnXOff);
            PortHandShake.Items.Add(Handshake.XOnXOff);
            PortHandShake.SelectedIndex = 0;

            Diagnostics.Text = "Ready";

            m_diagCallback = new DiagCallback(Log);
            m_threadFinishedCallback = new ThreadFinishedCallback(ThreadFinished);

            m_stopThread = new ManualResetEvent(false);
        }

        bool m_running = false;
        ManualResetEvent m_stopThread;
        private void SendDataBtn_Click(object sender, EventArgs e)
        {
            if (m_running)
            {
                //
                // Stop running thread
                m_running = false;
                m_stopThread.Set();
                SendDataBtn.Text = "Send";
            }
            else
            {
                //
                // Start a new thread
                Diagnostics.Text = "";
                m_running = true;
                SendDataBtn.Text = "Stop";
                m_stopThread.Reset();

                DataEntryType det;
                if (HexData.Checked)
                {
                    det = DataEntryType.HexValues;
                }
                else if (BinaryData.Checked)
                {
                    det = DataEntryType.BitPattern;
                }
                else
                {
                    det = DataEntryType.Random;
                }

                JobDetails job = new JobDetails()
                {
                    m_COMPort = PortName.Text,
                    m_BaudRate = Convert.ToInt32(PortBaudRate.Text),
                    m_Parity = (Parity)PortParity.SelectedItem,
                    m_DataBits = Convert.ToInt32(DataBits.Text),
                    m_StopBits = (StopBits)PortStopBits.SelectedItem,
                    m_Encoding = (Encoding)PortEncoding.SelectedItem,
                    m_Timeout = Convert.ToInt32(PortWriteTimeout.Text),
                    m_Delay = Convert.ToInt32(SendDelay.Text),
                    m_Handshake = (Handshake)PortHandShake.SelectedItem,
                    m_Iterations = Convert.ToInt32(Iterations.Text),
                    m_DataEntryType = det,
                    m_HexValues = HexValue.Text,
                    m_BitValue = (byte)((Bit8.Checked ? 1 << 7 : 0) |
                                        (Bit7.Checked ? 1 << 6 : 0) |
                                        (Bit6.Checked ? 1 << 5 : 0) |
                                        (Bit5.Checked ? 1 << 4 : 0) |
                                        (Bit4.Checked ? 1 << 3 : 0) |
                                        (Bit3.Checked ? 1 << 2 : 0) |
                                        (Bit2.Checked ? 1 << 1 : 0) |
                                        (Bit1.Checked ? 1 << 0 : 0))
                };
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), job);
            }
        }

        //
        // Define some callbacks. Have to do this because you can't access any GUI elements directly from worker threads...
        delegate void DiagCallback(string msg);
        delegate void ThreadFinishedCallback();
        DiagCallback m_diagCallback;
        ThreadFinishedCallback m_threadFinishedCallback;
        void Log(string msg)
        {
            Diagnostics.Text += "\r\n[" + DateTime.Now.ToString() + "]: " + msg;
            Diagnostics.Select(Diagnostics.TextLength, 1);
            Diagnostics.ScrollToCaret();
        }

        void ThreadFinished()
        {
            SendDataBtn.Text = "Send";
            m_running = false;
            Log("Serial thread stopped");
        }

        //
        // Main worker thread. 
        private void ThreadProc(object stateInfo)
        {
            Diagnostics.Invoke(m_diagCallback, new object[] { "Serial thread started" });
            JobDetails job = (JobDetails)stateInfo;
            try
            {
                //
                // Open serial port using the supplied configuration
                Diagnostics.Invoke(m_diagCallback, new object[] { "Opening serial port..." });
                using (SerialPort port = new SerialPort(job.m_COMPort,
                    job.m_BaudRate, job.m_Parity, job.m_DataBits, job.m_StopBits))
                {
                    port.WriteTimeout = job.m_Timeout;
                    port.Handshake = job.m_Handshake;
                    port.Encoding = job.m_Encoding;
                    port.Open();
                    port.DtrEnable = true;

                    //
                    // Pause until port is in the open state...
                    Diagnostics.Invoke(m_diagCallback, new object[] { "Waiting for serial port to open (press 'Stop' if nothing happens)..." });
                    while (!port.IsOpen && !m_stopThread.WaitOne(0))
                    {
                    }
                    Diagnostics.Invoke(m_diagCallback, new object[] { "Serial port is " + (port.IsOpen ? "Open" : "Closed") });
                    Diagnostics.Invoke(m_diagCallback, new object[] { "Current Serial Port Settings:" });
                    string settings = string.Format("\r\nName:{0}\r\nBaud:{1}\r\nParity:{2}\r\nDataBits:{3}\r\nStopBits:{4}\r\nHandshake:{5}\r\nWriteTimeout:{6}\r\nEncoding:{7}", port.PortName,
                        port.BaudRate, port.Parity, port.DataBits, port.StopBits,
                                                    port.Handshake, port.WriteTimeout, port.Encoding);
                    Diagnostics.Invoke(m_diagCallback, new object[] { settings });

                    //
                    // Prepare the serial data to send. This is either the supplied list of hex values, a single bit pattern or random data
                    List<byte> dataValues = new List<byte>();
                    if (job.m_DataEntryType == DataEntryType.HexValues)
                    {
                        string[] values = job.m_HexValues.Split(new char[] { ' ', ',' });
                        if (values != null && values.Length > 0)
                        {
                            foreach (string value in values)
                            {
                                dataValues.Add(Convert.ToByte(value, 16));
                            }
                        }
                    }
                    else if (job.m_DataEntryType == DataEntryType.BitPattern)
                    {
                        dataValues.Add(job.m_BitValue);
                    }

                    //
                    // Now pump out the data values one by one until the number of iterations has been exhausted.
                    if (job.m_DataEntryType != DataEntryType.Random && dataValues.Count == 0)
                    {
                        Diagnostics.Invoke(m_diagCallback, new object[] { "Supplied data value(s) invalid." });
                    }
                    else
                    {
                        int dataIndex = 0;
                        Random rnd = new Random((int)DateTime.Now.ToBinary());

                        for (int byteCount = 0; (byteCount < job.m_Iterations) && !m_stopThread.WaitOne(0); byteCount++)
                        {
                            byte value;
                            if (job.m_DataEntryType == DataEntryType.Random)
                            {
                                value = (byte)(rnd.Next() % (byte.MaxValue + 1));
                            }
                            else
                            {
                                value = dataValues[dataIndex++];
                            }

                            string text = string.Format("Iteration [{0}] - Writing Hex Byte [#{1:x}]", byteCount, value);
                            Diagnostics.Invoke(m_diagCallback, new object[] { text });
                            port.Write(new byte[] { value }, 0, 1);
                            if (job.m_DataEntryType != DataEntryType.Random)
                            {
                                dataIndex = dataIndex % dataValues.Count;
                            }
                            Thread.Sleep(job.m_Delay);
                        }
                    }
                    port.Close();
                }
            }
            catch (Exception exp)
            {
                Diagnostics.Invoke(m_diagCallback, new object[] { "Exception caught:\r\n" + exp.Message });
            }
            finally
            {
                ThreadFinished();
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            m_stopThread.Set();
        }
    }
}
